import os
import sys
import importlib
import weakref

import KBEDebug as log
import KBEngine


g_allReloadObjs = []


class Reload:
    """
    不是继承于KBEngine.Entity的类，如果也想热更新需要继承Reload
    """

    def __init__(self):
        g_allReloadObjs.append(weakref.ref(self))


def reloadPyCls(reloadPaths):
    """
    热更新。不能热更涉及到引擎层的py模块，比如KBEngine.Entity
    """
    entitiesXMLAP = KBEngine.matchPath('scripts/entities.xml')
    pyScriptRootPath = entitiesXMLAP.split('entities.xml')[0]
    dontReloadFile = {'__init__', 'KBEngine'}

    log.DEBUG_MSG('pyScriptRootPath: %s' % pyScriptRootPath)
    """
    {
        'BattleSimulator': <module 'BattleSimulator' from 'D:/CocosCreator/BaiTan/baitan-server/scripts/common\\BattleSimulator.py'>,
        'cfgmgr': <module 'cfgmgr' from 'D:/CocosCreator/BaiTan/baitan-server/scripts/common\\cfgmgr.py'>,
        'db': <module 'db' from 'D:/CocosCreator/BaiTan/baitan-server/scripts/common\\db.py'>
    }
    """
    allNewModule = {}
    for reloadPath, pkgName in reloadPaths.items():
        fileDir = os.path.join(pyScriptRootPath, reloadPath)
        log.DEBUG_MSG(f'fileDir: {fileDir}')
        for root, dir, files in os.walk(fileDir):
            if fileDir != root:
                break

            for file in files:
                fileName, fileSuffix = file.split('.')
                if fileName in dontReloadFile or fileSuffix != 'py':
                    continue

                modName = fileName
                if pkgName != '':
                    modName = f'{pkgName}.{fileName}'

                log.DEBUG_MSG(f'modName={modName}')
                mod = sys.modules.get(modName)
                # 有的模块不在sys.modules中（为什么？代码执行到需要改模块的语句时，才会导入该模块，模块就被缓存到sys.modules中）
                if mod is None:
                    log.DEBUG_MSG(f'Not found module = {fileName}')
                    continue

                log.DEBUG_MSG(f'reloaded module: {fileName}')
                allNewModule[modName] = importlib.reload(mod)

    return allNewModule


def reloadData():
    """
    只热更新配置表
    """
    reloadPaths = {
        'data': '',
    }
    reloadPyCls(reloadPaths)

# 这个只是测试用，不要用在正式项目，因为还有些动力没理清
def reloadModule():
    """
    热更新

    注意：
        1、给实体添加新的存储属性，热更新后虽然内存能获取该属性，但该属性不会存盘；
        2、hotupdate只会热更当前进程中的代码，如果有多个baseapp和cellapp，要确保每个baseapp和cellapp所在的进程都执行hotupdate
    """
    # 目录->包名
    reloadPaths = {
        'data': '',
        'common': '',
        'server_common': ''
    }

    componentName = KBEngine.component
    log.DEBUG_MSG('componentName: %s' % componentName)
    if componentName == 'baseapp':
        reloadPaths['base/ilobby'] = 'ilobby'
        reloadPaths['base/interfaces'] = 'interfaces'
        reloadPaths['base/components'] = 'components'
        reloadPaths['base'] = ''
    elif componentName == 'cellapp':
        reloadPaths['cell/interfaces'] = 'interfaces'
        reloadPaths['cell/components'] = 'components'
        reloadPaths['cell'] = ''
    else:
        log.DEBUG_MSG('fcomponent: {componentName}')
        return
    # 实体热更
    allNewModule = reloadPyCls(reloadPaths)
    for entity in KBEngine.entities.values():
        entityNewModule = allNewModule.get(entity.className)
        if entityNewModule is None:
            log.WARNING_MSG(f'reloadModule className {entity.className} not found')
            continue

        entity.__class__ = getattr(entityNewModule, entity.className)

    # 继承了Reload对象热更
    for refObj in g_allReloadObjs:
        obj = refObj()
        if obj is None:
            g_allReloadObjs.remove(refObj)
            log.WARNING_MSG('refObj is None')
            continue
        className = obj.__class__.__name__
        newModule = allNewModule.get(className)
        if newModule is None:
            log.WARNING_MSG(f'reloadModule className {className} not found')
            continue
        obj.__class__ = getattr(newModule, className)

    # 在def文件里面添加了新的方法或者属性，必须调用，否则不生效
    # 如果添加的是persistent为true的属性，热更后数据库表里面并不会写入该属性字段，对于这种情况必须重启引擎
    success = KBEngine.reloadScript()
    log.DEBUG_MSG(f'KBEngine.reloadScript is {success}')


def reload_base_logic(file_path):
    """
    hotupdate.reload_base_logic('components/AvatarEquipmentLogic.py')
    """
    entities_xml = KBEngine.matchPath('scripts/entities.xml')
    py_root_path = entities_xml.split('entities.xml')[0]
    file_full_path = os.path.join(py_root_path, 'base', file_path)
    print(file_full_path)
    if os.path.exists(file_full_path):
        _, file_name = os.path.split(file_full_path)
        module_name, _ = os.path.splitext(file_name)
        mod = sys.modules.get(module_name)
        # 有的模块不在sys.modules中（为什么？代码执行到需要该模块的语句时，才会导入该模块，模块就被缓存到sys.modules中）
        if mod is None:
            log.DEBUG_MSG(f'Not found module = {module_name} in sys.modules')
        else:
            log.DEBUG_MSG(f'reloaded module: {module_name}')
        importlib.reload(mod)


def reload_common():
    reloadPaths = {
        # 'data': '',
        'common': '',
        # 'server_common': ''
    }
    reloadPyCls(reloadPaths)


def reload_interface():
    reloadPaths = {
        'interface': 'interface'
    }
    reloadPyCls(reloadPaths)


def reload_base_systems():
    """
    加载base目录下的所有systems子目录的脚本
    systems目录类的脚本只写逻辑，不存数据
    """
    entities_xml = KBEngine.matchPath('scripts/entities.xml')
    py_root_path = entities_xml.split('entities.xml')[0]
    file_dir = os.path.join(py_root_path, 'base')
    for root, dir, files in os.walk(file_dir):
        if '__pycache__' in root:
            continue
        for file in files:
            file_name, file_suffix = file.split('.')
            if file_suffix == 'py' and 'system' in file_name.lower():
                module_name = f'systems.{file_name}'
                print(module_name)
                mod = sys.modules.get(module_name)
                if mod is None:
                    log.ERROR_MSG(f'Not found module = {file_name} in sys.modules')
                else:
                    log.DEBUG_MSG(f'reloaded module: {file_name}')
                    importlib.reload(mod)
